﻿/*
   Copyright 2013 Orando Labs

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Concurrent;

using OrandoLabs.Common;
using OrandoLabs.Inquiro;
using OrandoLabs.EnduroStore;
using OrandoLabs.Inquiro.EsentApi;
using OrandoLabs.Inquiro.Stemmers;
using OrandoLabs.OpenSource.Common;

namespace OrandoLabs.OpenSource.Inquiro
{
    public class InquiroBulkLoader : IDisposable
    {
        protected string DbDir;
        protected string Name;
        protected string DbName;
        protected string ContextName;

        OrandoInstance Instance;
        EnduroInstance EInstance;
        InquiroInstance IInstance;
        protected EnduroDatabase EDb;
        protected InquiroDatabase IDb;
        protected EnduroStoreDatabase ESDb;
        
        public EnduroObjectStore Store;
        public DocumentManagerObject DocManager;
        
        public InquiroBulkLoader(String name, String dir)
        {
            Name = name;
            DbDir = dir;
            DbName = name + ".edb";
            ContextName = "iqbulk_context_" + name;
            Instance = new OrandoInstance(name, dir);
            EInstance = new EnduroInstance(Instance);
            EDb = new EnduroDatabase(EInstance, DbName);
            ESDb = new EnduroStoreDatabase("iqbulk_store_" + name, EDb);
            IInstance = new InquiroInstance(dir, InstanceIdWrapper.GetInstanceId(Instance));
            EsentApi api = new EsentApi();
            Api.SetApi(api);
            IDb = new InquiroDatabase(IInstance, DbName);
            Store = new EnduroObjectStore(ESDb, "myp", "http://my.namespace.com");
            Store.DeclareObject<DocumentManagerObject>();
            Store.DeclareObject<DocumentObject>();
        }

        public async Task<bool> Initialize()
        {
            DocManager = await Store.FetchObjectFromIdAsync<DocumentManagerObject>(Name);
            if (DocManager == null)
            {
                DocManager = Store.NewObject<DocumentManagerObject>(Name);
                DocManager.Name = Name;
                await Store.FlushAsync(false);
            }
            return true;
        }

        public void Dispose()
        {
            Instance.Dispose();
        }

        public virtual TextTokenizer GetTokenizer(TextReader reader)
        {
            return new TextTokenizer(reader);
        }
    }

    public class InquiroBulkFolderLoader : InquiroBulkLoader
    {
        int Location;
        String Folder;
        int TargetIdx;
        Target Target;
        int TotalTerms;
        IStemmer Stemmer;
        bool KeepStats = true;
        protected InquiroContext Ctx;
        public InquiroBulkFolderLoader(String name, String dbDir, String folder, bool noStats)
            : this(name, dbDir, folder, null, noStats)
        {
        }
        public InquiroBulkFolderLoader(String name, String dbDir, String folder)
            : this(name, dbDir, folder, null, false)
        {
        }
        public InquiroBulkFolderLoader(String name, String dbDir, String folder, IStemmer stemmer, bool noStats) 
            : base(name, dbDir)
        {
            Folder = folder;
            Stemmer = stemmer;
            KeepStats = !noStats;
        }

        public async Task<bool> Load(Func<Target,bool> progress)
        {
            await Initialize();
            using (Ctx = new InquiroContext(IDb, ContextName, true, KeepStats))
            {
                string[] files = Directory.GetFiles(Folder);
                foreach (String file in files)
                {
                    TextReader reader = File.OpenText(file);
                    Target = await NewTarget(file);
                    TextTokenizer tokenizer = GetTokenizer(reader);
                    while (true)
                    {
                        String token = await tokenizer.NextToken();
                        if (token.Length == 0) break;
                        ProcessToken(token);
                    }
                    if (!progress(Target))
                        break;
                    TargetDone();
                }
                Ctx.BulkLoadDone();
                LoadComplete();
                return true;
            }
        }

        public virtual void LoadComplete()
        {
        }

        List<Term> Terms = new List<Term>();
        public virtual void ProcessToken(String token)
        {
            Location++;
            token = token.ToLowerInvariant();
            TextTerm term = new TextTerm(token, Target, Location);
            Terms.Add(term);
            AddStemTerm(token);
        }

        public virtual void AddStemTerm(string token)
        {
            string stem;
            if (Stemmer != null && Stemmer.Stem(token, out stem))
            {
                Term term = new Term(stem, Kinds.StemKind, Target, Location);
                Terms.Add(term);
            }
        }

        public virtual void TargetDone()
        {
            Ctx.AddTermsForTarget(Terms.ToArray());
            TotalTerms += Terms.Count;
            Terms = new List<Term>();
            Location = 0;
        }

        public async virtual Task<Target> NewTarget(String path)
        {
            Target target = new Target(++TargetIdx);
            await TrackTarget(target, path);
            return target;
        }

        public async virtual Task<bool> TrackTarget(Target target, String path)
        {
            DocumentObject doc = Store.NewObject<DocumentObject>();
            doc.Target = target;
            doc.Name = path;
            DocManager.AddDocument(doc);
            await Store.FlushAsync(false);
            return true;
        }
    }

    [EnduroClass("documentmanager")]
    public class DocumentManagerObject : EnduroObject
    {
        ConcurrentDictionary<String, DocumentObject> DocumentsByNameDictionary;
        ConcurrentDictionary<Target, DocumentObject> DocumentsByTargetDictionary;
        public DocumentManagerObject()
            : base()
        {
        }

        [EnduroProperty("name")]
        public string Name
        {
            get
            {
                return ToString("name");
            }
            set
            {
                SetValue("name", value);
            }
        }

        public void InitializeDictionaries()
        {
            if (DocumentsByTargetDictionary == null)
            {
                DocumentsByNameDictionary = new ConcurrentDictionary<String, DocumentObject>();
                DocumentsByTargetDictionary = new ConcurrentDictionary<Target, DocumentObject>();
                List<DocumentObject> docs = ToList<DocumentObject>("documents");
                foreach (DocumentObject doc in docs)
                {
                    DocumentsByNameDictionary[doc.Name] = doc;
                    DocumentsByTargetDictionary[doc.Target] = doc;
                }
            }
        }

        [EnduroProperty("documents")]
        public ConcurrentDictionary<Target, DocumentObject> DocumentsByTarget
        {
            get
            {
                InitializeDictionaries();
                return DocumentsByTargetDictionary;
            }
        }

        public ConcurrentDictionary<String, DocumentObject> DocumentsByName
        {
            get
            {
                InitializeDictionaries();
                return DocumentsByNameDictionary;
            }
        }

        public void AddDocument(DocumentObject o)
        {
            InitializeDictionaries();
            DocumentsByNameDictionary[o.Name] = o;
            DocumentsByTargetDictionary[o.Target] = o;
            AddObjectValue("documents", o);
        }

        public void RemoveDocument(DocumentObject o)
        {
            DocumentObject value;
            InitializeDictionaries();
            DocumentsByNameDictionary.TryRemove(o.Name, out value);
            DocumentsByTargetDictionary.TryRemove(o.Target, out value);
            RemoveObjectValue("documents", o);
        }
    }

    [EnduroClass("document")]
    public class DocumentObject : EnduroObject, IComparable
    {
        public DocumentObject()
            : base()
        {
        }

        [EnduroProperty("name")]
        public string Name
        {
            get
            {
                return ToString("name");
            }
            set
            {
                SetValue("name", value);
            }
        }

        [EnduroProperty("target")]
        public Target Target
        {
            get
            {
                return new Target(ToInt("target"));
            }
            set
            {
                SetValue<int>("target", value.target);
            }
        }

        public override int GetHashCode()
        {
            return Target.target;
        }

        public override bool Equals(object obj)
        {
            DocumentObject that = obj as DocumentObject;
            return this.Target.target == that.Target.target;
        }

        public int CompareTo(object obj)
        {
            DocumentObject that = obj as DocumentObject;
            return this.Target.CompareTo(that.Target);
        }

    }
}